package com.csw.compose.ui.module.component.custom

import android.util.Log
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.withFrameMillis
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.csw.compose.theme.pagePadding
import com.csw.compose.ui.Destination
import com.csw.compose.ui.components.BodyText
import com.csw.compose.ui.components.LabelText
import com.csw.compose.ui.components.NormalSpacer
import com.csw.compose.ui.components.PrimaryTitle
import com.csw.compose.ui.components.UlItem
import com.csw.compose.ui.module.BasePage
import kotlinx.coroutines.isActive
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.cos
import kotlin.math.floor

@Preview
@Composable
fun CustomDrawPagePreview() {
    BasePage({}, "自定义组件-绘制") {
        CustomDrawPage()
    }
}

class CustomDrawPageData {
    companion object : Destination("CustomDrawPage")
}

@Composable
fun CustomDrawPage() {
    Column(
        modifier = Modifier
            .padding(horizontal = pagePadding)
            .verticalScroll(rememberScrollState())
    ) {
        PrimaryTitle("简介")
        BodyText("一个组件在屏幕上呈现需要经过 测量->放置->绘制三个过程，这里主要讲怎么通过绘制功能制作自定义组件。")
        NormalSpacer()

        CircleLoading(
            modifier = Modifier.size(90.dp)
        )
        UlItem("Canvas")
        LabelText("画布组件，简单粗暴，他提供了与Android Canvas一样的API用于场景变换与图形绘制。")
        LabelText("由于没有Paint，Compose将画笔的设置移动到了画布API中，默认的图形绘制都是抗锯齿的。")
    }
}

/**
 * 圆形loading组件，组件将执行动画，表现为堆叠的圆形卡纸轮番折叠起来
 * @param modifier 修饰器
 * @param colors 颜色数组
 * @param duration 一轮动画时长（四个方向各折叠一次为一轮）
 */
@Composable
fun CircleLoading(
    modifier: Modifier,
    colors: List<Color> = listOf(Color.Red, Color.Green, Color.Blue),
    duration: Long = 2000L,
) {
    val startTime = remember { System.currentTimeMillis() }
    val colorIndex = remember { mutableIntStateOf(0) }
    val progress = remember { mutableFloatStateOf(0f) }
    LaunchedEffect(Unit) {
        while (isActive) {
            //启用附带效应，在协程执行期间逐步更新动画进度,若组件在重组后从屏幕移除,协程会被中止
            progress.floatValue =
                (System.currentTimeMillis() - startTime) * 1f % duration / duration
            colorIndex.intValue =
                ((System.currentTimeMillis() - startTime) * 4f / duration).toInt() % colors.size

            //控制更新频率,固定60帧每秒
//            delay(1000 / 60)
            //有的设备刷新率可以达到甚至超过120FPS，这里可以参考View根据垂直同步信号（VSYNC）来实现动画刷新与屏幕刷新同步
            //正常来讲我们应该通过系统API（Choreographer.getInstance().postFrameCallback()）监听刷帧，然后在回调中执行下一帧动画的计算
            //不过这里Compose已经通过suspendCancellableCoroutine把回调封装成一个挂起函数了，在下一帧刷新时协程会恢复
            //通常手机的刷新率也是60FPS，这里这里反复的监听与取消监听会不会有点浪费性能
            withFrameMillis {
                Log.d("frameTime", it.toString())
            }
        }
    }
    Canvas(modifier = modifier) {
        val radius = size.minDimension / 2//取宽高的最小值计算半径
        val preColor = colors[(colorIndex.intValue - 1 + colors.size) % colors.size]
        val currColor = colors[colorIndex.intValue]
        drawCircle(colors[colorIndex.intValue], radius)//绘制下个色卡
        val step = (progress.floatValue * 4).toInt()//翻转动画由上下左右四个方向各执行一次
        rotate(step * -90f) {//逆时针转动好看点
            //绘制当前色卡的下半部分
            drawArc(
                preColor,
                0f,
                180f,
                true,
                Offset(center.x - radius, center.y - radius),
                Size(radius * 2, radius * 2)
            )
            //绘制翻转中的另一半色卡
            val stepProgress = progress.floatValue * 4 - step//当前步骤的进度
            val angle = stepProgress * 180//整个步骤匀速翻转180度
            val radian = angle / 180 * PI//换算弧度角
            val cos = cos(radian).toFloat()//翻转过程以半径为斜边，通过翻转角度计算得到投影高度
            val projectedHeight = abs(cos) * radius
            if (stepProgress < 0.5f) {
                //不可避免的浮点值问题，会导致上下两半的图形中间留了一条缝隙，这里通过绘制一像素的线条进行修复
                drawLine(
                    preColor,
                    Offset(center.x - radius, center.y),
                    Offset(center.x + radius, center.y)
                )
                drawArc(
                    preColor,
                    180f,
                    180f,
                    true,
                    Offset(center.x - radius, center.y - projectedHeight),
                    Size(radius * 2, projectedHeight * 2)
                )
            } else {
                drawLine(
                    currColor,
                    Offset(center.x - radius, center.y),
                    Offset(center.x + radius, center.y)
                )
                drawArc(
                    currColor,
                    0f,
                    180f,
                    true,
                    Offset(center.x - radius, center.y - projectedHeight),
                    Size(radius * 2, projectedHeight * 2)
                )
            }
        }
    }
}